"""
pip install pycryptodome==3.20.0
pip install pyOpenSSL==24.2.1
"""
import json
from datetime import datetime
from urllib.parse import parse_qs
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from base64 import encodebytes, decodebytes
from urllib.parse import quote_plus
import hashlib
import OpenSSL

from django.conf import settings


class AliPay(object):
    def __init__(self):
        self.is_pem = settings.ALI_IS_PEM
        self.app_id = settings.ALI_APP_ID
        self.gateway = settings.ALI_GATEWAY

        if self.is_pem:
            # 证书签名
            with open(settings.ALI_PEM_APP_CERT_PUBLIC_KEY_PATH) as fp:
                self.ali_pem_app_cert_public_key_str = fp.read()

            with open(settings.ALI_PEM_APP_PRIVATE_PATH) as fp:
                self.ali_pem_app_private_str = fp.read()

            with open(settings.ALI_PEM_ALIPAY_ROOT_CERT_PATH) as fp:
                self.ali_pem_alipay_root_cert_str = fp.read()

            with open(settings.ALI_PEM_ALIPAY_CERT_PUBLIC_KEY_PATH) as fp:
                self.ali_pem_alipay_cert_public_key = fp.read()
        else:
            # 公钥签名
            with open(settings.ALI_RSA_PRIVATE_PATH) as fp:
                self.ali_rsa_private_str = fp.read()

            with open(settings.ALI_RSA_PUBLIC_PATH) as fp:
                self.ali_rsa_public_str = fp.read()

        self.notify_url = settings.ALI_NOTIFY_URL
        self.return_url = settings.ALI_RETURN_URL

    def signature(self, params):
        # 待签名的字符串排序
        unsigned_string = "&".join(["{0}={1}".format(k, params[k]) for k in sorted(params)])

        # SHA256WithRSA + 应用私钥 对待签名的字符串 进行签名
        if self.is_pem:
            private_key = RSA.importKey(self.ali_pem_app_private_str)
        else:
            private_key = RSA.importKey(self.ali_rsa_private_str)
        signer = PKCS1_v1_5.new(private_key)
        signature = signer.sign(SHA256.new(unsigned_string.encode('utf-8')))

        # 对签名之后的执行进行base64 编码，转换为字符串
        sign_string = encodebytes(signature).decode("utf8").replace('\n', '')
        return sign_string

    def pay_url(self, order_id, total_amount, subject):
        params = {
            'app_id': self.app_id,
            'method': 'alipay.trade.page.pay',
            'format': 'JSON',
            'charset': 'utf-8',
            'sign_type': 'RSA2',
            'notify_url': self.notify_url,
            'return_url': self.return_url,
            'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            'version': '1.0',
            'biz_content': json.dumps({
                'out_trade_no': order_id,
                'product_code': 'FAST_INSTANT_TRADE_PAY',
                'total_amount': total_amount,
                'subject': subject
            }, separators=(',', ':'))
        }

        if self.is_pem:
            params['app_cert_sn'] = self.get_app_cert_sn()
            params['alipay_root_cert_sn'] = self.get_root_cert_sn()

        sign_string = self.signature(params)
        result = "&".join(["{0}={1}".format(k, quote_plus(params[k])) for k in sorted(params)])
        param_string = result + "&sign=" + quote_plus(sign_string)

        pay_url = "{}?{}".format(self.gateway, param_string)
        return pay_url

    def get_app_cert_sn(self):
        cert = OpenSSL.crypto.load_certificate(
            OpenSSL.crypto.FILETYPE_PEM, self.ali_pem_app_cert_public_key_str.encode('ascii')
        )
        cert_issue = cert.get_issuer()
        name = f'CN={cert_issue.CN},OU={cert_issue.OU},O={cert_issue.O},C={cert_issue.C}'
        m = hashlib.md5()
        m.update(bytes(f'{name}{cert.get_serial_number()}', encoding='utf-8'))
        return m.hexdigest()

    def get_root_cert_sn(self):
        root_cert_sn = None
        for cert in (
                OpenSSL.crypto.load_certificate(
                    OpenSSL.crypto.FILETYPE_PEM, c.encode('ascii')
                )
                for c in self.ali_pem_alipay_root_cert_str.split('\n\n')
        ):
            try:
                alg = cert.get_signature_algorithm()
            except ValueError:
                continue
            if b'rsaEncryption' in alg or b'RSAEncryption' in alg:
                cert_issue = cert.get_issuer()
                name = f'CN={cert_issue.CN},OU={cert_issue.OU},O={cert_issue.O},C={cert_issue.C}'
                m = hashlib.md5()
                m.update(bytes(f'{name}{cert.get_serial_number()}', encoding="utf8"))
                cert_sn = m.hexdigest()
                if not root_cert_sn:
                    root_cert_sn = cert_sn
                else:
                    root_cert_sn = f'{root_cert_sn}_{cert_sn}'
        return root_cert_sn

    def verify(self, params, signature):
        unsigned_string = "&".join(["{0}={1}".format(k, params[k]) for k in sorted(params)])

        if self.is_pem:
            cert = OpenSSL.crypto.load_certificate(
                OpenSSL.crypto.FILETYPE_PEM, self.ali_pem_alipay_cert_public_key.encode('ascii')
            )
            public_key = RSA.importKey(
                OpenSSL.crypto.dump_publickey(OpenSSL.crypto.FILETYPE_PEM, cert.get_pubkey()).decode()
            )
        else:
            public_key = RSA.importKey(self.ali_rsa_public_str)

        signer = PKCS1_v1_5.new(public_key)
        digest = SHA256.new()
        digest.update(unsigned_string.encode("utf8"))
        verify = signer.verify(digest, decodebytes(signature.encode("utf8")))
        return verify
